#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "global_var.h"
#include "socket.h"
#include "check.h"
#include "handle.h"

void handle_in(struct epoll_event *event)
{
	income_t *income = (income_t *)event->data.ptr;

	if (income->recv_DATA_flag != 1)
	{
		unsigned char buf[512]; // The maximum total length of a command line including the command word and the <CRLF> is 512 characters.
		int buf_len = recv(income->fd, buf, 512, 0);
		if (buf_len >= 512) // line too long
		{
			unsigned char unused_buf[512];
			while (0)
			{
				int un = recv(income->fd, unused_buf, 512, 0);
				if (un == -1 || un == 0)
					break;
			}
			income->ans_code = a500;
			event->events = EPOLLOUT | EPOLLRDHUP | EPOLLET;
			epoll_ctl(income->belong_epfd, EPOLL_CTL_MOD, income->fd, event);
		}
		else if (buf_len == -1) // receive error
		{
			income->ans_code = a500;
			// TODO: 进一步处理errno
			event->events = EPOLLOUT | EPOLLRDHUP | EPOLLET;
			epoll_ctl(income->belong_epfd, EPOLL_CTL_MOD, income->fd, event);
		}
		else if (buf_len < 6) // line too short
		{
			income->ans_code = a500;
			event->events = EPOLLOUT | EPOLLRDHUP | EPOLLET;
			epoll_ctl(income->belong_epfd, EPOLL_CTL_MOD, income->fd, event);
		}
		else if (buf_len >= 6)
		{
			unsigned char *crlf = strstr(buf, "\r\n");
			int buf_cmd_len = 0;
			if (crlf != NULL)
			{
				buf_cmd_len = crlf - buf;
				*crlf = '\0';
			}

			unsigned char main_cmd[5] = {0, 0, 0, 0, 0}; // 4 byte main command
			memcpy(main_cmd, buf, 4);

			if (buf_cmd_len < 4) // command too short
			{
				income->ans_code = a500;
			}
			else if (strcmp(main_cmd, "HELP") == 0)
			{
				income->ans_code = a502;
			}
			else if (strcmp(main_cmd, "EXPN") == 0)
			{
				income->ans_code = a502;
			}
			else if (strcmp(main_cmd, "VRFY") == 0)
			{
				income->ans_code = a502;
			}
			else if (strcmp(main_cmd, "NOOP") == 0)
			{
				income->ans_code = a250;
			}
			else if (strcmp(main_cmd, "RSET") == 0)
			{
				income->recv_DATA_flag = 0;
				destory_mail_store(income->mail_store);
				income->mail_store = NULL;
				income->ans_code = a250;
			}
			else if (strcmp(main_cmd, "QUIT") == 0)
			{
				if (income->recv_DATA_flag != 2 && income->mail_store != NULL)
				{
					destory_mail_store(income->mail_store);
					income->mail_store = NULL;
				}
				// TODO free income or something else?
				income->ans_code = a221;
			}
			else if (strcmp(main_cmd, "HELO") == 0)
			{
				if (income->mail_store == NULL)
					income->mail_store = init_mail_store(NULL);
				if ((buf_cmd_len - 5) > 0 && (buf_cmd_len - 5) < 256)
					if (strchr(buf + 5, ' ') != NULL) // more than one parameter
					{
						income->ans_code = a504;
					}
					else if (income->mail_store->mailfrom[0] == '\0' || income->recv_DATA_flag == 2) // HELO before MAIL or after <CRLF>.<CRLF>
					{
						memcpy(income->mail_store->hostname, buf + 5, (buf_cmd_len - 5));
						if (income->addr.ss_family == AF_INET)
						{
							inet_ntop(AF_INET, &((struct sockaddr_in *)&(income->addr))->sin_addr, income->mail_store->ip, 48);
						}
						else if (income->addr.ss_family == AF_INET6)
						{
							memcpy(income->mail_store->ip, "IPv6:", 5); // ipv6 addr prefix "IPv6:"
							inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&(income->addr))->sin6_addr, (income->mail_store->ip) + 5, 48);
						}
						income->ans_code = a250;
					}
					else
					{
						income->ans_code = a503;
					}
				else
					income->ans_code = a501;
			}
			else if (strcmp(main_cmd, "EHLO") == 0)
			{
				if (income->mail_store == NULL)
					income->mail_store = init_mail_store(NULL);
				if ((buf_cmd_len - 5) > 0 && (buf_cmd_len - 5) < 256)
					if (strchr(buf + 5, ' ') != NULL) // more than one parameter
					{
						income->ans_code = a504;
					}
					else if (income->mail_store->mailfrom[0] == '\0' || income->recv_DATA_flag == 2) // EHLO before MAIL or after <CRLF>.<CRLF>
					{
						memcpy(income->mail_store->hostname, buf + 5, (buf_cmd_len - 5));
						if (income->addr.ss_family == AF_INET)
						{
							inet_ntop(AF_INET, &((struct sockaddr_in *)&(income->addr))->sin_addr, income->mail_store->ip, 48);
						}
						else if (income->addr.ss_family == AF_INET6)
						{
							memcpy(income->mail_store->ip, "IPv6:", 5); // ipv6 addr prefix "IPv6:"
							inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&(income->addr))->sin6_addr, (income->mail_store->ip) + 5, 48);
						}
						income->ans_code = a250EHLO;
					}
					else
					{
						income->ans_code = a503;
					}
				else
					income->ans_code = a501;
			}
			else if (strcmp(main_cmd, "MAIL") == 0)
			{
				if (income->mail_store == NULL || income->mail_store->hostname[0] == '\0' || income->mail_store->rcpt_n != 0) // mailfrom must after helo and before rept
				{
					income->ans_code = a503;
				}
				else if ((buf_cmd_len - 5) >= 7 && strncmp(buf + 5, "FROM:<", 6) == 0 && check_reverse_path(buf + 10, buf_cmd_len - 10) == 0) // "MAIL FROM:<...>"
				{
					strncpy(income->mail_store->mailfrom, buf + 10, buf_cmd_len - 10);
					income->declare_size = get_declare_size(buf + 10, buf_cmd_len - 10);
					if (income->recv_DATA_flag == 2)
						income->recv_DATA_flag = 0;
					income->ans_code = a250;
				}
				else
				{
					income->ans_code = a550; // 501
				}
			}
			else if (strcmp(main_cmd, "RCPT") == 0)
			{
				if (income->mail_store == NULL || income->mail_store->mailfrom[0] == '\0' || income->recv_DATA_flag > 0) // mailfrom must after mailfrom and before data
				{
					income->ans_code = a503;
				}
				else if (income->declare_size > g_max_mail_size)
				{
					income->ans_code = a552;
				}
				else if ((buf_cmd_len - 5) >= 5 && strncmp(buf + 5, "TO:<", 4) == 0 && check_forward_path(buf + 8, buf_cmd_len - 8) == 0) // "RCPT TO:<...>"
				{
					if (income->mail_store->rcpt_n < 100)
					{
						strncpy(income->mail_store->rcptto[income->mail_store->rcpt_n], buf + 8, buf_cmd_len - 8);
						income->mail_store->rcpt_n += 1;
						income->ans_code = a250;
					}
					else
					{
						income->ans_code = a452;
					}
				}
				else
				{
					income->ans_code = a550; // 501
				}
			}
			else if (strcmp(main_cmd, "DATA") == 0)
			{
				if (income->mail_store != NULL && income->mail_store->rcpt_n > 0)
				{
					income->mail_store->recv_time = time(NULL);
					gen_mail_id(income->mail_store, income->mail_store->id, 48);
					income->recv_DATA_flag = 1;
					income->ans_code = a354;
				}
				else
				{
					income->ans_code = a503;
				}
			}
			else
			{
				income->ans_code = a500;
			}

			event->events = EPOLLOUT | EPOLLRDHUP | EPOLLET;
			epoll_ctl(income->belong_epfd, EPOLL_CTL_MOD, income->fd, event);
		}
	}
	else if (income->recv_DATA_flag == 1) // receive data
	{
		unsigned char buf[1024] = {0};
		int recv_len;
		for (recv_len = recv(income->fd, buf, 1024, 0); recv_len != -1; recv_len = recv(income->fd, buf, 1024, 0))
		{
			if (recv_len > 0)
			{
				size_t dl = income->mail_store->data_len + recv_len;
				unsigned char *d = malloc(sizeof(unsigned char) * (dl + 1)); // tail '\0'
				memset(d, 0, sizeof(unsigned char) * (dl + 1));
				if (income->mail_store->data != NULL)
				{
					strncpy(d, income->mail_store->data, income->mail_store->data_len);
					free(income->mail_store->data);
				}
				strncpy(d + income->mail_store->data_len, buf, recv_len);

				income->mail_store->data = d;
				income->mail_store->data_len = dl;

				// search CRLF.CRLF or ^.CRLF
				unsigned char *p = strstr(d, "\r\n.\r\n");
				if (strncmp(d, ".\r\n", 3) == 0)
					p = d;
				if (p != NULL)
				{
					memset(p, 0, sizeof(unsigned char) * (dl - (p - d)));
					income->mail_store->data_len = p - d;

					// flag=2
					income->recv_DATA_flag = 2;
					// TODO pass data
					// TODO clean mail_store -> mailto & rcpt & recv_time

					income->ans_code = a250;
					event->events = EPOLLOUT | EPOLLRDHUP | EPOLLET;
					epoll_ctl(income->belong_epfd, EPOLL_CTL_MOD, income->fd, event);
					return;
				}
			}
		}
		// do not change events status
	}
}

void handle_out(struct epoll_event *event)
{
	income_t *income = (income_t *)event->data.ptr;

	if (send(income->fd, ans_string[income->ans_code], strlen(ans_string[income->ans_code]), 0) == -1)
	{
		event->events = EPOLLOUT | EPOLLRDHUP | EPOLLET;
		epoll_ctl(income->belong_epfd, EPOLL_CTL_MOD, income->fd, event);
	}
	else if (income->ans_code == a221 || income->ans_code == a421)
	{
		epoll_ctl(income->belong_epfd, EPOLL_CTL_DEL, income->fd, event);
		close_socket(income->fd);
		free(income);
	}
	else
	{
		event->events = EPOLLIN | EPOLLRDHUP | EPOLLET;
		epoll_ctl(income->belong_epfd, EPOLL_CTL_MOD, income->fd, event);
	}
}

int *handle_start(handle_thread_t *thread)
{
	struct epoll_event events[10];
	while (1)
	{
		int ewn = epoll_wait(thread->epfd, events, 10, 2000);
		if (ewn == -1)
		{
			fprintf(stderr, "epoll_wait return -1, %d\n", errno);
		}
		else
		{
			for (int i = 0; i < ewn; i++)
			{
				if (events[i].events & EPOLLRDHUP)
				{
					income_t *income = (income_t *)events[i].data.ptr;
					close_socket(income->fd);
					epoll_ctl(thread->epfd, EPOLL_CTL_DEL, income->fd, events + i);
					destory_mail_store(income->mail_store);
					free(income);
				}
				else if (events[i].events & EPOLLIN)
				{
					handle_in(events + i);
				}
				else if (events[i].events & EPOLLOUT)
				{
					handle_out(events + i);
				}
				else
				{
					income_t *income = (income_t *)events[i].data.ptr;
					send(income->fd, ans_string[a554], strlen(ans_string[a554]), 0);
					close_socket(income->fd);
					epoll_ctl(thread->epfd, EPOLL_CTL_DEL, income->fd, events + i);
					destory_mail_store(income->mail_store);
					free(income);
				}
			}
		}
	}

	return NULL;
}